package gwtgaetools.client.comm;

import gwtgaetools.shared.model.GenericMessage;

import com.google.gwt.appengine.channel.client.Channel;
import com.google.gwt.appengine.channel.client.ChannelFactory;
import com.google.gwt.appengine.channel.client.ChannelFactory.ChannelCreatedCallback;
import com.google.gwt.appengine.channel.client.Socket;
import com.google.gwt.appengine.channel.client.SocketError;
import com.google.gwt.appengine.channel.client.SocketListener;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamFactory;
import com.google.gwt.user.client.rpc.SerializationStreamReader;

/**
 * Client-side rpc class for client-server communication
 * @author opowell
 *
 */
public abstract class GenericClientRPC<
Type,
MainObject,
RPCService extends GenericRPCServiceAsync<Type, Message>, 
Message extends GenericMessage<Type>, 
MessageInterpreter extends GenericMessageInterpreter<Type, Message, ?>
> {

	private MessageInterpreter interpreter;
	
	public Socket socket;
	public String channelToken;
	public String clientId;
	
	public MainObject dataModel;
	public RPCService rpcService;	
	protected AsyncCallback<Void> emptyCallback = new AsyncCallback<Void>() {

		@Override
		public void onFailure(Throwable caught) {}

		@Override
		public void onSuccess(Void result) {}

	}; 

	public GenericClientRPC(MainObject m) {
		dataModel = m;
		interpreter = createInterpreter();
		rpcService = createRPCService();
		
	}

	protected abstract MessageInterpreter createInterpreter();

	protected abstract RPCService createRPCService();
	
	// Default: do nothing.
	public void reset() {}

//	public void askForChannelToken(final String traderID) {
//		rpcService.askForChannelToken(traderID, new AsyncCallback<ArrayList<String>>() {
//
//			@Override
//			public void onFailure(Throwable caught) {
//				System.out.println("GenericClientRPC.askForChannelToken(): error " + caught);
//			}
//
//			@Override
//			public void onSuccess(ArrayList<String> results) {
//				channelToken = results.get(0);
//				createChannel(results.get(0), results.get(1), traderID);
//			}
//
//		});
//	}
	
	public void onSocketOpen(String cId) {
		System.out.println("Channel opened!");
		clientId = cId;
	}
	public void onSocketClose() {
		System.out.println("Channel closed! Asking for new token...");
		reconnectChannel();
	}
	
	public void createChannel(final String channelToken, final String cId) {
		if (socket!=null) 
			socket.close();
		this.channelToken = channelToken;
		ChannelFactory.createChannel(channelToken, new ChannelCreatedCallback() {
			public void onChannelCreated(final Channel channelResult) {
				socket = channelResult.open(new SocketListener() {
					public void onOpen() {
						onSocketOpen(cId);
					}

					public void onMessage(final String encodedData) {
				        try {
//				            SerializationStreamReader reader = ((SerializationStreamFactory)rpcService).createStreamReader(encodedData);
//				            @SuppressWarnings("unchecked")
//							ArrayList<Message> messages = (ArrayList<Message>) reader.readObject();
//							for (Message message : messages)

				        	SerializationStreamReader reader = ((SerializationStreamFactory)rpcService).createStreamReader(encodedData);
				            @SuppressWarnings("unchecked")
							Message message = (Message) reader.readObject();
							
				            processMessage(message);
				          } catch (SerializationException e) {
				            throw new RuntimeException("Unable to deserialize " + encodedData, e);
				          }
					}

					public void onError(SocketError error) {
						channelError(error);
					}

					public void onClose() {
						onSocketClose();
					}
				});
			}
		});
	}
	
	public void channelError(SocketError error) {
		System.out.println("Error: " + error.getDescription());
	}
	
	public abstract void reconnectChannel();
	
	public void processMessage(Message s) {
		try {
			interpreter.processMessage(s);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	
	public AsyncCallback<Void> getEmptyCallback() {
		return emptyCallback;
	}
	
	public void sendMessage(Message msg) {
		sendMessage(msg, emptyCallback);
	}
	
	public void sendMessage(Message msg, AsyncCallback<Void> ascb) {
		rpcService.processMessage(msg, ascb);
	}
	
}